/*
 * Copyright © 2009 Perseus Project - Tufts University <http://www.perseus.tufts.edu>
 *
 * This file is part of UniCollatorPerseus.
 *
 * OcroPerseus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * OcroPerseus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with OcroPerseus.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.himeros.ocr.hocr;

import eu.himeros.alignment.StringArrayAligner;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import eu.himeros.transcoder.TransHandler;
import eu.himeros.util.IoManager;
import eu.himeros.util.Matrix;
import java.io.BufferedReader;
import java.util.Vector;

/**
 * Mix editions in a djvu.xml file.
 *
 * @author Federico Boschetti <federico.boschetti.73@gmail.com>
 */
public class EditionsMixer extends TransHandler{
    Stack<String> stk=new Stack<String>();
    XMLReader xr = null;
    boolean inSpan=false;
    boolean sameSeq=false;
    boolean inLine=false;
    String line="";
    Vector<Vector> lines=new Vector<Vector>(100);
    Vector<String[]> charCoords=new Vector<String[]>(100);
    int lid=0; //line id
    int wid=0; //word id
    BufferedReader gtBr=IoManager.getReader(fileName+"-gt.csv");
    BufferedReader ocrBr=IoManager.getReader(fileName+".csv");
    BufferedWriter djvuBw=IoManager.getWriter(fileName+"_djvu.xml");
    static String fileName=null;
    Vector<Integer> posGtV=new Vector<Integer>(1000);
    Vector<Integer> posOcrV=new Vector<Integer>(1000);
    Vector<Integer> freqGtV=new Vector<Integer>(1000);
    Vector<Integer> freqOcrV=new Vector<Integer>(1000);
    Vector<String> wordGtV=new Vector<String>(1000);
    Vector<String> wordOcrV=new Vector<String>(1000);
    Vector<String> gtAlgWordV=new Vector<String>(1000);
    Vector<String> gtAlgPosV=new Vector<String>(1000);
    Vector<String> ocrAlgWordV=new Vector<String>(1000);
    Vector<String> ocrAlgPosV=new Vector<String>(1000);
    Matrix algMx=new Matrix(); //alignment matrix
    Integer one=new Integer(1);
    StringArrayAligner alg=new StringArrayAligner();
    String[][] alignedChunks=null;
    int gtCounter=0;
    int ocrCounter=0;
    String spanId=null;
    String spanCoords=null;
    WordMixer wm=new WordMixer();

    /**
     * Default Constructor.
     */
    public EditionsMixer(){
        super();
        String[] items=null;
        try{
            while((line=gtBr.readLine())!=null){
                items=line.split("\t");
                posGtV.add(Integer.valueOf(items[0]));
                freqGtV.add(Integer.valueOf(items[1]));
                wordGtV.add(items[2]);
            }
            while((line=ocrBr.readLine())!=null){
               items=line.split("\t");
                posOcrV.add(Integer.valueOf(items[0]));
                freqOcrV.add(Integer.valueOf(items[1]));
                wordOcrV.add(items[2]);
            }
        }catch(Exception ex){ex.printStackTrace();}
        alignUniqueWords();
        alignChunks();
    }

    /**
     * Parse the file.
     *
     * @param inFile the input file.
     * @param outFile the output file.
     */
    public void parse(String inFile, String outFile) {
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
            xr = XMLReaderFactory.createXMLReader();
            xr.setProperty("http://xml.org/sax/properties/lexical-handler",this);
            xr.setContentHandler(this);

            xr.setErrorHandler(this);
            xr.parse(inFile);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * StartDocument parsing method.
     */
    @Override
    public void startDocument(){
        try{
        //bw.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"+
        bw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
           "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"+
           "<head>"+
           "<meta content=\"ocr_line ocr_page\" name=\"ocr-capabilities\"/>\n"+
           "<meta content=\"en\" name=\"ocr-langs\"/>\n"+
           "<meta content=\"Latn\" name=\"ocr-scripts\"/>\n" +
           "<meta content=\"\" name=\"ocr-microformats\"/>\n"+
           "<title>OCR Output</title></head>\n<body>");

        //djvu START #1
        djvuBw.write("<?xml version=\"1.0\"?>\n"+
            "<!DOCTYPE DjVuXML PUBLIC \"-//W3C//DTD DjVuXML 1.1//EN\" \"pubtext/DjVuXML-s.dtd\">\n"+
            "<DjVuXML>\n"+
            "<HEAD>file://localhost/media/lacie/ocr/ath-meineke/djvu/ath35.djvu</HEAD>\n"+
            "<BODY>\n"+
            "<OBJECT data=\"file://localhost/media/lacie/ocr/ath-meineke/djvu/ath35.djvu\" type=\"image/x.djvu\" height=\"1967\""+
            " width=\"1275\" usemap=\"ath35-194.djvu\">\n" +
            "<PARAM name=\"DPI\" value=\"100\"/>\n"+
            "<PARAM name=\"GAMMA\" value=\"2.200000\"/>\n"+
            "<PARAM name=\"PAGE\" value=\"ath35-194.djvu\"/>\n"+
            "<HIDDENTEXT>\n"+
            "<PAGECOLUMN>\n"+
            "<REGION>\n"+
            "<PARAGRAPH>\n");
        //djvu END #1
        }catch(Exception ex){ex.printStackTrace();}
    }

    /**
     * StartElement parsing method.
     *
     * @param uri
     * @param localName
     * @param qName
     * @param attributes
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        try{
            if(qName.equals("div")||qName.equals("p")){
                if(qName.equals("div")||qName.equals("p")) bw.write("\n");
                    writeStartElement=true;
                    super.startElement(uri, localName, qName, attributes);
                    writeStartElement=false;
                    //djvu START #1
                    if(qName.equals("div")&&attributes.getValue("class").equals("ocr_line")){
                        inLine=true;
                        djvuBw.write("<LINE>\n");
                    }
                    //djvu END #1
            }else if(qName.equals("span")&&attributes.getValue("class").equals("ocr_line")){
                writeStartElement=true;
                super.startElement(uri, localName, qName, attributes);
                writeStartElement=false;
            }else if(qName.equals("span")&&attributes.getValue("class").equals("ocr_word")){
                spanId=attributes.getValue("id");
                spanCoords=attributes.getValue("coords");
            }
        }catch(Exception ex){ex.printStackTrace();}
    }

    /**
     * EndElement parsing method.
     *
     * @param uri
     * @param localName
     * @param qName
     * @throws org.xml.sax.SAXException
     */
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        String gtChunk="";
        String ocrChunk="";
        String color=null;
        try{
            if(qName.equals("div")||qName.equals("p")||qName.equals("span")){
                if(qName.equals("div")||qName.equals("p")) bw.write("\n");
                //djvu START #1
                if(qName.equals("div")&&inLine){
                    djvuBw.write("</LINE>\n");
                    inLine=false;
                }
                //djvu END #1
                if(qName.equals("span")){
                    ocrChunk=ocrAlgWordV.get(gtCounter);
                    gtChunk=gtAlgWordV.get(gtCounter);
                    if(gtChunk==null) gtChunk="#";
                    while((gtCounter<(ocrAlgWordV.size()-1))&&ocrAlgWordV.get(gtCounter+1)==null){
                        gtCounter++;
                        gtChunk+=" "+gtAlgWordV.get(gtCounter);
                    }
                    gtCounter++;
                    System.out.println("gtChunk: "+gtChunk+" - ocrChunk: "+ocrChunk);
                    ocrChunk=wm.merge(gtChunk,ocrChunk);
                    color=(gtChunk.equals(ocrChunk)?"black":"red");
                    bw.write("<span id=\""+spanId+"\" class=\"ocr_word\" coords=\""+spanCoords+"\"");
                    bw.write(" title=\""+gtChunk+"\"");
                    bw.write(" style=\"color:"+color+"\"");
                    bw.write(">");
                    bw.write(ocrChunk);
                    //djvu START #2
                    djvuBw.write("<WORD coords=\""+djvuCoords(spanCoords)+"\">"+ocrChunk+"</WORD>\n");
                    //djvu END #2
                    gtChunk="";
                    ocrChunk="";
                }
                writeEndElement=true;
                super.endElement(uri, localName, qName);
                writeEndElement=false;
                if(qName.contains("span")) bw.write("\n");
            }
        }catch(Exception ex){ex.printStackTrace();}
    }

    @Override
    /**
     * End element parsing method.
     */
    public void endDocument(){
        try{
            bw.write("</body>\n</html>\n");
            bw.close();
            //djvu START #1
            djvuBw.write("</PARAGRAPH>\n"+
                    "</REGION>\n"+
                    "</PAGECOLUMN>\n"+
                    "</HIDDENTEXT>\n"+
                    "</OBJECT>\n" +
                    "<MAP name=\"ath35-194.djvu\"/>\n"+
                    "<OBJECT data=\"file://localhost/media/lacie/ocr/ath-meineke/djvu/ath35.djvu\" type=\"image/x.djvu\""+
                    " height=\"1967\" width=\"1275\" usemap=\"ath35-195.djvu\">\n"+
                    "<PARAM name=\"DPI\" value=\"100\"/>\n"+
                    "<PARAM name=\"GAMMA\" value=\"2.200000\"/>\n"+
                    "<PARAM name=\"PAGE\" value=\"ath35-195.djvu\"/>\n"+
                    "<HIDDENTEXT/>\n"+
                    "</OBJECT>\n"+
                    "<MAP name=\"ath35-195.djvu\"/>\n"+
                    "</BODY>\n"+
                    "</DjVuXML>\n");
            djvuBw.close();
            //djvu END #1

        }catch(Exception ex){ex.printStackTrace();}
    }

    /**
     * Align unique words.
     */
    //TODO substitute with ngrams!
    private void alignUniqueWords(){
        int freqGt=0;
        int j=0;
        int prevVal=Integer.MAX_VALUE;
        int val=0;
        String wordGt=null;
        for(int i=0;i<freqGtV.size();i++){
            freqGt=freqGtV.get(i).intValue();
            if(freqGt==1){
                wordGt=wordGtV.get(i);
                j=wordOcrV.indexOf(wordGt);
                if(j>-1&&freqOcrV.get(j).intValue()==1&&j>=i) algMx.add(i,j);
            }
        }
        //discard (real or apparent) transpositions: START #1
        for(int i=algMx.size()-1;i>-1;i--){
            val=algMx.get(i)[1];
            if(prevVal<val) algMx.remove(i);
            prevVal=val;
        } // END #1
    }

    /**
     * Align the chunks.
     */
    private void alignChunks(){
        Vector<String> gtChunkV=new Vector<String>(200);
        Vector<String> ocrChunkV=new Vector<String>(200);
        int startGt=0,startOcr=0;
        algMx.add(posGtV.lastElement().intValue(),posOcrV.lastElement().intValue());
        for(int i=0;i<algMx.size();i++){
            for(int j=startGt;j<algMx.get(i)[0];j++){
                gtChunkV.add(wordGtV.get(j));
            }
            startGt=algMx.get(i)[0];
            for(int j=startOcr;j<algMx.get(i)[1];j++){
                ocrChunkV.add(wordOcrV.get(j));
            }
            startOcr=algMx.get(i)[1];
            String[] s1=new String[gtChunkV.size()];
            gtChunkV.toArray(s1);
            String[] s2=new String[ocrChunkV.size()];
            ocrChunkV.toArray(s2);
            alignedChunks=alg.align(s1,s2);
            for(int k=alignedChunks[0].length-1;k>-1;k--){
                gtAlgWordV.add(alignedChunks[0][k]);
                ocrAlgWordV.add(alignedChunks[1][k]);
            }
            gtChunkV.removeAllElements();
            ocrChunkV.removeAllElements();
        }
    }

    /**
     * Format djvu coordinates
     * @param coordsStr space separated coordinates.
     * @return comma separated coordinates.
     */
    private String djvuCoords(String coordsStr){
        String[] items=coordsStr.split(" ");
        String res=items[1]+","+items[2]+","+items[3]+","+items[4];
        return res;
    }

   /**
    * The <code>main</code> method.
    * 
    * @param args the command line arguments.
    */
   public static void main(String[] args) {
       if(args==null||args.length==0){
           System.out.println("Usage: eu.himeros.ocr.OcrHtml2DjvuXml <fileNameWithoutExtension>");
       }
       fileName=args[0];
       EditionsMixer em=new EditionsMixer();
       em.parse(fileName+"-x.html", fileName+"-xx.html");
   }

}
